/*
 * SVG Salamander
 * Copyright (c) 2004, Mark McKay
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or 
 * without modification, are permitted provided that the following
 * conditions are met:
 *
 *   - Redistributions of source code must retain the above 
 *     copyright notice, this list of conditions and the following
 *     disclaimer.
 *   - Redistributions in binary form must reproduce the above
 *     copyright notice, this list of conditions and the following
 *     disclaimer in the documentation and/or other materials 
 *     provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE. 
 * 
 * Mark McKay can be contacted at mark@kitfox.com.  Salamander and other
 * projects can be found at http://www.kitfox.com
 *
 * Created on January 26, 2004, 5:21 PM
 */

package com.kitfox.svg;

import java.awt.AlphaComposite;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Composite;
import java.awt.Graphics2D;
import java.awt.Paint;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;

import com.kitfox.svg.Marker.MarkerLayout;
import com.kitfox.svg.Marker.MarkerPos;
import com.kitfox.svg.xml.StyleAttribute;

/**
 * Parent of shape objects
 *
 * @author Mark McKay
 * @author <a href="mailto:mark@kitfox.com">Mark McKay</a>
 */
abstract public class ShapeElement extends RenderableElement {

	/**
	 * This is necessary to get text elements to render the stroke the correct
	 * width. It is an alternative to producing new font glyph sets at different
	 * sizes.
	 */
	protected float strokeWidthScalar = 1f;

	/** Creates a new instance of ShapeElement */
	public ShapeElement() {
	}

	@Override
	abstract public void render(Graphics2D g) throws SVGException;

	/*
	 * protected void setStrokeWidthScalar(float strokeWidthScalar) {
	 * this.strokeWidthScalar = strokeWidthScalar; }
	 */

	@Override
	void pick(Point2D point, boolean boundingBox, List retVec)
			throws SVGException {
		// StyleAttribute styleAttrib = new StyleAttribute();
		// if (getStyle(styleAttrib.setName("fill")) &&
		// getShape().contains(point))
		if ((boundingBox ? getBoundingBox() : getShape()).contains(point)) {
			retVec.add(getPath(null));
		}
	}

	@Override
	void pick(Rectangle2D pickArea, AffineTransform ltw, boolean boundingBox,
			List retVec) throws SVGException {
		// StyleAttribute styleAttrib = new StyleAttribute();
		// if (getStyle(styleAttrib.setName("fill")) &&
		// getShape().contains(point))
		if (ltw.createTransformedShape(
				(boundingBox ? getBoundingBox() : getShape()))
				.intersects(pickArea)) {
			retVec.add(getPath(null));
		}
	}

	private Paint handleCurrentColor(StyleAttribute styleAttrib)
			throws SVGException {
		if (styleAttrib.getStringValue().equals("currentColor")) {
			StyleAttribute currentColorAttrib = new StyleAttribute();
			if (getStyle(currentColorAttrib.setName("color"))) {
				if (!currentColorAttrib.getStringValue().equals("none")) {
					return currentColorAttrib.getColorValue();
				}
			}
			return null;
		}
		return styleAttrib.getColorValue();
	}

	protected void renderShape(Graphics2D g, Shape shape) throws SVGException {
		// g.setColor(Color.green);

		StyleAttribute styleAttrib = new StyleAttribute();

		// Don't process if not visible
		if (getStyle(styleAttrib.setName("visibility"))) {
			if (!styleAttrib.getStringValue().equals("visible")) {
				return;
			}
		}

		if (getStyle(styleAttrib.setName("display"))) {
			if (styleAttrib.getStringValue().equals("none")) {
				return;
			}
		}

		// None, solid color, gradient, pattern
		Paint fillPaint = Color.black; // Default to black. Must be explicitly
										// set to none for no fill.
		if (getStyle(styleAttrib.setName("fill"))) {
			if (styleAttrib.getStringValue().equals("none")) {
				fillPaint = null;
			} else {
				fillPaint = handleCurrentColor(styleAttrib);
				if (fillPaint == null) {
					URI uri = styleAttrib.getURIValue(getXMLBase());
					if (uri != null) {
						Rectangle2D bounds = shape.getBounds2D();
						AffineTransform xform = g.getTransform();

						SVGElement ele = diagram.getUniverse().getElement(uri);
						if (ele != null) {
							fillPaint = ((FillElement) ele).getPaint(bounds,
									xform);
						}
					}
				}
			}
		}

		// Default opacity
		float opacity = 1f;
		if (getStyle(styleAttrib.setName("opacity"))) {
			opacity = styleAttrib.getRatioValue();
		}

		float fillOpacity = opacity;
		if (getStyle(styleAttrib.setName("fill-opacity"))) {
			fillOpacity *= styleAttrib.getRatioValue();
		}

		Paint strokePaint = null; // Default is to stroke with none
		if (getStyle(styleAttrib.setName("stroke"))) {
			if (styleAttrib.getStringValue().equals("none")) {
				strokePaint = null;
			} else {
				strokePaint = handleCurrentColor(styleAttrib);
				if (strokePaint == null) {
					URI uri = styleAttrib.getURIValue(getXMLBase());
					if (uri != null) {
						Rectangle2D bounds = shape.getBounds2D();
						AffineTransform xform = g.getTransform();

						SVGElement ele = diagram.getUniverse().getElement(uri);
						if (ele != null) {
							strokePaint = ((FillElement) ele).getPaint(bounds,
									xform);
						}
					}
				}
			}
		}

		float[] strokeDashArray = null;
		if (getStyle(styleAttrib.setName("stroke-dasharray"))) {
			strokeDashArray = styleAttrib.getFloatList();
			if (strokeDashArray.length == 0) {
				strokeDashArray = null;
			}
		}

		float strokeDashOffset = 0f;
		if (getStyle(styleAttrib.setName("stroke-dashoffset"))) {
			strokeDashOffset = styleAttrib.getFloatValueWithUnits();
		}

		int strokeLinecap = BasicStroke.CAP_BUTT;
		if (getStyle(styleAttrib.setName("stroke-linecap"))) {
			String val = styleAttrib.getStringValue();
			if (val.equals("round")) {
				strokeLinecap = BasicStroke.CAP_ROUND;
			} else if (val.equals("square")) {
				strokeLinecap = BasicStroke.CAP_SQUARE;
			}
		}

		int strokeLinejoin = BasicStroke.JOIN_MITER;
		if (getStyle(styleAttrib.setName("stroke-linejoin"))) {
			String val = styleAttrib.getStringValue();
			if (val.equals("round")) {
				strokeLinejoin = BasicStroke.JOIN_ROUND;
			} else if (val.equals("bevel")) {
				strokeLinejoin = BasicStroke.JOIN_BEVEL;
			}
		}

		float strokeMiterLimit = 4f;
		if (getStyle(styleAttrib.setName("stroke-miterlimit"))) {
			strokeMiterLimit = Math.max(styleAttrib.getFloatValueWithUnits(),
					1);
		}

		float strokeOpacity = opacity;
		if (getStyle(styleAttrib.setName("stroke-opacity"))) {
			strokeOpacity *= styleAttrib.getRatioValue();
		}

		float strokeWidth = 1f;
		if (getStyle(styleAttrib.setName("stroke-width"))) {
			strokeWidth = styleAttrib.getFloatValueWithUnits();
		}
		// if (strokeWidthScalar != 1f)
		// {
		strokeWidth *= strokeWidthScalar;
		// }

		Marker markerStart = null;
		if (getStyle(styleAttrib.setName("marker-start"))) {
			if (!styleAttrib.getStringValue().equals("none")) {
				URI uri = styleAttrib.getURIValue(getXMLBase());
				markerStart = (Marker) diagram.getUniverse().getElement(uri);
			}
		}

		Marker markerMid = null;
		if (getStyle(styleAttrib.setName("marker-mid"))) {
			if (!styleAttrib.getStringValue().equals("none")) {
				URI uri = styleAttrib.getURIValue(getXMLBase());
				markerMid = (Marker) diagram.getUniverse().getElement(uri);
			}
		}

		Marker markerEnd = null;
		if (getStyle(styleAttrib.setName("marker-end"))) {
			if (!styleAttrib.getStringValue().equals("none")) {
				URI uri = styleAttrib.getURIValue(getXMLBase());
				markerEnd = (Marker) diagram.getUniverse().getElement(uri);
			}
		}

		// Draw the shape
		if (fillPaint != null && fillOpacity != 0f) {
			if (fillOpacity <= 0) {
				// Do nothing
			} else if (fillOpacity < 1f) {
				Composite cachedComposite = g.getComposite();
				g.setComposite(AlphaComposite
						.getInstance(AlphaComposite.SRC_OVER, fillOpacity));

				g.setPaint(fillPaint);
				g.fill(shape);

				g.setComposite(cachedComposite);
			} else {
				g.setPaint(fillPaint);
				g.fill(shape);
			}
		}

		if (strokePaint != null && strokeOpacity != 0f) {
			BasicStroke stroke;
			if (strokeDashArray == null) {
				stroke = new BasicStroke(strokeWidth, strokeLinecap,
						strokeLinejoin, strokeMiterLimit);
			} else {
				stroke = new BasicStroke(strokeWidth, strokeLinecap,
						strokeLinejoin, strokeMiterLimit, strokeDashArray,
						strokeDashOffset);
			}

			Shape strokeShape;
			AffineTransform cacheXform = g.getTransform();
			if (vectorEffect == VECTOR_EFFECT_NON_SCALING_STROKE) {
				strokeShape = cacheXform.createTransformedShape(shape);
				strokeShape = stroke.createStrokedShape(strokeShape);
			} else {
				strokeShape = stroke.createStrokedShape(shape);
			}

			if (strokeOpacity <= 0) {
				// Do nothing
			} else {
				Composite cachedComposite = g.getComposite();

				if (strokeOpacity < 1f) {
					g.setComposite(AlphaComposite.getInstance(
							AlphaComposite.SRC_OVER, strokeOpacity));
				}

				if (vectorEffect == VECTOR_EFFECT_NON_SCALING_STROKE) {
					// Set to identity
					g.setTransform(new AffineTransform());
				}

				g.setPaint(strokePaint);
				g.fill(strokeShape);

				if (vectorEffect == VECTOR_EFFECT_NON_SCALING_STROKE) {
					// Set to identity
					g.setTransform(cacheXform);
				}

				if (strokeOpacity < 1f) {
					g.setComposite(cachedComposite);
				}
			}
		}

		if (markerStart != null || markerMid != null || markerEnd != null) {
			MarkerLayout layout = new MarkerLayout();
			layout.layout(shape);

			ArrayList list = layout.getMarkerList();
			for (int i = 0; i < list.size(); ++i) {
				MarkerPos pos = (MarkerPos) list.get(i);

				switch (pos.type) {
				case Marker.MARKER_START:
					if (markerStart != null) {
						markerStart.render(g, pos, strokeWidth);
					}
					break;
				case Marker.MARKER_MID:
					if (markerMid != null) {
						markerMid.render(g, pos, strokeWidth);
					}
					break;
				case Marker.MARKER_END:
					if (markerEnd != null) {
						markerEnd.render(g, pos, strokeWidth);
					}
					break;
				}
			}
		}
	}

	abstract public Shape getShape();

	protected Rectangle2D includeStrokeInBounds(Rectangle2D rect)
			throws SVGException {
		StyleAttribute styleAttrib = new StyleAttribute();
		if (!getStyle(styleAttrib.setName("stroke"))) {
			return rect;
		}

		double strokeWidth = 1;
		if (getStyle(styleAttrib.setName("stroke-width"))) {
			strokeWidth = styleAttrib.getDoubleValue();
		}

		rect.setRect(rect.getX() - strokeWidth / 2,
				rect.getY() - strokeWidth / 2, rect.getWidth() + strokeWidth,
				rect.getHeight() + strokeWidth);

		return rect;
	}

}
